package com.ewili.framework.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import org.apache.commons.beanutils.ConvertUtils;
import org.springframework.util.Assert;

import java.beans.PropertyDescriptor;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

/**
 * 用于操作java bean、集合、数组的工具类
 *
 * @author leo
 */
public final class BeanUtils {

    // bean转换用到的mapper
    private static final MapperFacade beanMapper;
    private static final MapperFacade ignoreNullBeanMapper;
    private static final ObjectMapper objectMapper;

    static {
        // 没有查到怎么使用一个mapper同时实现全量拷贝和ignoreNull，先这样写
        beanMapper = new DefaultMapperFactory.Builder().build().getMapperFacade();
        ignoreNullBeanMapper = new DefaultMapperFactory.Builder().mapNulls(false).build().getMapperFacade();
        objectMapper = new ObjectMapper();
    }

    private BeanUtils() {
    }

    /**
     * 将对象转换为json
     *
     * @param obj
     * @return
     */
    public static String toJson(Object obj) {
        try {
            return objectMapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    /**
     * 将json转为对象。
     *
     * @param json
     * @param clazz
     * @param clazz
     * @return
     */
    public static <T> T fromJson(String json, Class<T> clazz) {
        try {
            return objectMapper.readValue(json, clazz);
        } catch (IOException e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    /**
     * 将json转为对象。
     * <p>
     * 典型用法：
     * <p>
     * {@code Map<String, Set<Integer>> map = BeanUtils.fromJson(}<br>
     * {@code   json, new TypeReference<Map<String, Set<Integer>>>()); }
     * </p>
     *
     * @param json
     * @param type
     * @return
     */
    public static <T> T fromJson(String json, TypeReference<T> type) {
        try {
            return objectMapper.readValue(json, type);
        } catch (IOException e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    /**
     * 将pojo的属性名与属性值简单的解析出来。（请注意：使用内省实现，性能不佳。）
     *
     * @param obj
     * @return
     */
    public static String toString(Object obj) {
        if (obj == null) {
            return null;
        }
        if (obj.getClass().isArray()) {
            return Arrays.toString((Object[]) obj);
        }
        if (obj instanceof Collection || obj instanceof Map) {
            return obj.toString();
        }
        try {
            Map<String, String> map = org.apache.commons.beanutils.BeanUtils.describe(obj);
            for (Iterator<Entry<String, String>> it = map.entrySet().iterator(); it.hasNext(); ) {
                Entry<String, String> entry = it.next();
                if (entry.getValue() == null) {// 值为空的不显示
                    it.remove();
                }
            }
            map.remove("class");
            return "[" + obj.getClass().getSimpleName() + "] " + map.toString();
        } catch (Exception e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    /**
     * 将字符串转为指定的类型。
     * 使用BeanUtils实现，默认情况下支持基本类型的包装类型。
     * 注意如果入参为null或空字符串，返回值可能不为空。比如在clazz为Integer.class时返回是0。
     * 如果需要在入参为null时返回null，请使用MathUtils.create*方法。
     *
     * @param str
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <V> V convert(String str, Class<V> clazz) {
        return (V) ConvertUtils.convert(str, clazz);
    }

    /**
     * 判断对象为空
     *
     * @param obj 对象名
     * @return 是否为空
     */
    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        }
        if ((obj instanceof List)) {
            return ((List) obj).size() == 0;
        }
        if ((obj instanceof String)) {
            return "".equals(((String) obj).trim());
        }
        return false;
    }

    /**
     * 判断对象不为空
     *
     * @param obj 对象名
     * @return 是否不为空
     */
    public static boolean notEmpty(Object obj) {
        return !isEmpty(obj);
    }

    /**
     * 判断是否为空
     *
     * @param ary
     * @return
     */
    public static boolean notEmpty(Object[] ary) {
        return !isEmpty(ary);
    }

    /**
     * 判断是否为空
     *
     * @param ary
     * @return
     */
    public static boolean isEmpty(Object[] ary) {
        return ary == null || ary.length <= 0;
    }

    /**
     * 判断是否为空
     *
     * @param collection
     * @return
     */
    public static boolean notEmpty(Collection<?> collection) {
        return !isEmpty(collection);
    }

    /**
     * 判断是否为空
     *
     * @param collection
     * @return
     */
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.isEmpty();
    }

    /**
     * 不为null但是是empty
     *
     * @param collection
     * @return
     */
    public static boolean notNullButEmpty(Collection<?> collection) {
        return collection != null && collection.isEmpty();
    }

    /**
     * 判断是否为空
     *
     * @param map
     * @return
     */
    public static boolean notEmpty(Map<?, ?> map) {
        return !isEmpty(map);
    }

    /**
     * 判断是否为空
     *
     * @param map
     * @return
     */
    public static boolean isEmpty(Map<?, ?> map) {
        return map == null || map.isEmpty();
    }

    /**
     * 不为null但是是empty
     *
     * @param map
     * @return
     */
    public static boolean notNullButEmpty(Map<?, ?> map) {
        return map != null && map.isEmpty();
    }

    /**
     * 判断下标是否在list中越界
     *
     * @param list
     * @param index
     * @return true 越界, false 没有越界
     */
    public static boolean isOutOfBounds(Collection<?> list, int index) {
        if (list == null) {
            return true;
        }
        return index < 0 || index >= list.size();
    }

    /**
     * 判断下标是否在array中越界
     *
     * @param ary
     * @param index
     * @return true 越界, false 没有越界
     */
    public static boolean isOutOfBounds(Object[] ary, int index) {
        if (ary == null) {
            return true;
        }
        return index < 0 || index >= ary.length;
    }

    /**
     * 将list转换为array
     *
     * @param list
     * @param clazz
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] toArray(List<T> list, Class<T> clazz) {
        T[] ary = (T[]) Array.newInstance(clazz, list.size());
        return list.toArray(ary);
    }

    /**
     * 删除list中的重复元素
     * 注意：请根据实际情况重写equals方法。
     *
     * @param list
     * @return
     */
    public static <T> void distinct(List<T> list) {
        List<T> result = distinctToNewList(list);
        list.clear();
        list.addAll(result);
    }

    /**
     * 删除list中的重复元素
     * 注意：请根据实际情况重写equals方法。
     *
     * @param list
     * @return
     */
    public static <T> List<T> distinctToNewList(List<T> list) {
        List<T> result = new ArrayList<T>(list.size());
        for (int i = 0; i < list.size(); i++) {
            T t = list.get(i);
            // 查看新集合中是否有指定的元素，如果没有则加入
            if (!result.contains(t)) {
                result.add(t);
            }
        }
        return result;
    }

    /**
     * 抽取集合中每个对象的指定属性并返回list
     *
     * @param collection 待抽取的集合
     * @return 抽取到的属性的集合
     */
    @SuppressWarnings("unchecked")
    public static <T> List<T> extractProperty(Collection<?> collection, String propertyName) {
        if (isEmpty(collection)) {
            return Collections.emptyList();
        }
        List<T> result = new ArrayList<T>(collection.size());
        for (Object obj : collection) {
            if (obj == null) {
                continue;
            }
            result.add(getProperty(obj, propertyName));
        }
        return result;
    }

    /**
     * 抽取集合中每个对象的两个属性并返回Map
     *
     * @param collection        待抽取的集合
     * @param keyPropertyName   要提取为Map中的Key值的属性名.
     * @param valuePropertyName 要提取为Map中的Value值的属性名.
     * @return 抽取到的属性的Map
     */
    @SuppressWarnings("unchecked")
    public static <K, V> Map<K, V> extractProperty(Collection<?> collection, String keyPropertyName,
                                                   String valuePropertyName) {
        if (isEmpty(collection)) {
            return Collections.emptyMap();
        }
        Map<K, V> result = new HashMap<>(collection.size());
        for (Object obj : collection) {
            if (obj == null) {
                continue;
            }
            result.put(getProperty(obj, keyPropertyName),
                    getProperty(obj, valuePropertyName));
        }
        return result;
    }

    /**
     * 取得List的第一个元素，如果List为空返回null.
     *
     * @param list
     * @return
     */
    public static <T> T getFirst(List<T> list) {
        if (isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    /**
     * 获取List的最后一个元素，如果List为空返回null.
     *
     * @param list
     * @return
     */
    public static <T> T getLast(List<T> list) {
        if (isEmpty(list)) {
            return null;
        }
        return list.get(list.size() - 1);
    }

    /**
     * 将源对象中的属性值，set到目标对象中去。
     *
     * @param source 源对象，用于get
     * @param target 目标对象，用于set
     */
    public static void copyProperties(Object source, Object target) {
        if (source == null || target == null) {
            return;
        }
        beanMapper.map(source, target);
    }

    /**
     * 将源对象中的属性值，set到目标对象中去，拷贝时忽略source中的null属性。
     *
     * @param source 源对象，用于get
     * @param target 目标对象，用于set
     */
    public static void copyPropertiesIgnoreNull(Object source, Object target) {
        if (source == null || target == null) {
            return;
        }
        ignoreNullBeanMapper.map(source, target);
    }

    /**
     * 将源对象中的属性值，set到目标对象中去。
     * <p>
     * 注意：
     * 1. 属性拷贝使用“源对象get方法”与“目标对象set方法”的名称做匹配。
     * 2. 类型不相同时，将自动类型转换，包括集合类型的泛型（如List<Integer>、List<String>可以被互相拷贝）。
     *
     * @param source     源对象，用于get
     * @param target     目标对象，用于set
     * @param ignoreNull 是否忽略null属性
     */
    public static void copyProperties(Object source, Object target, boolean ignoreNull) {
        if (source == null || target == null) {
            return;
        }
        if (ignoreNull) {
            ignoreNullBeanMapper.map(source, target);
        } else {
            beanMapper.map(source, target);
        }
    }

    /**
     * 调用pojo的get方法获取属性值
     *
     * @param obj
     * @param name
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getProperty(Object obj, String name) {
        PropertyDescriptor desc = getPropertyDescriptor(obj.getClass(), name);
        if (desc == null) {
            throw new IllegalArgumentException("property not found:" + name);
        }
        Method readMethod = desc.getReadMethod();
        if (readMethod == null) {
            throw new IllegalArgumentException("getter method not found:" + name);
        }
        try {
            return (T) readMethod.invoke(obj);
        } catch (Exception e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    /**
     * 调用pojo的set方法设置属性值
     *
     * @param obj
     * @param name
     * @param value
     */
    public static void setProperty(Object obj, String name, Object value) {
        PropertyDescriptor desc = getPropertyDescriptor(obj.getClass(), name);
        if (desc == null) {
            throw new IllegalArgumentException("property not found:" + name);
        }
        Method writeMethod = desc.getWriteMethod();
        if (writeMethod == null) {
            throw new IllegalArgumentException("setter method not found:" + name);
        }
        try {
            writeMethod.invoke(obj, value);
        } catch (Exception e) {
            throw ExceptionUtils.propagate(e);
        }
    }

    /**
     * 获取pojo的属性类型
     *
     * @param obj
     * @param name
     * @return
     */
    public static Class<?> getPropertyType(Object obj, String name) {
        PropertyDescriptor desc = getPropertyDescriptor(obj.getClass(), name);
        if (desc == null) {
            throw new IllegalArgumentException("property not found:" + name);
        }
        return desc.getPropertyType();
    }

    /**
     * 对集合中元素的指定属性求平均值。
     * <p>
     * 如，对User的age属性求平均值：
     * List<User> users = userService.findAll();
     * double avg = BeanUtils.avg(users, "age");
     *
     * @param pojoList
     * @param fieldName
     * @return
     */
    public static <T> double avg(List<T> pojoList, String fieldName) {
        Assert.notNull(pojoList);
        return avg(pojoList, fieldName, 0, pojoList.size());
    }

    /**
     * 对集合中元素的指定属性求平均值。（指定起始位置和向后计算的长度）
     *
     * @param pojoList
     * @param fieldName (只能是Number类型)
     * @param start     must >= 0
     * @param len       must >= 0
     * @return
     */
    public static <T> double avg(List<T> pojoList, String fieldName, int start, int len) {
        // 必须为正数
        Assert.isTrue(start >= 0, "start must >= 0");
        Assert.isTrue(len >= 0, "len must >= 0");
        // 验证是否非空
        Assert.notNull(pojoList);
        int end = start + len;
        // 验证是否可能越界
        if (isOutOfBounds(pojoList, end)) {
            throw new IndexOutOfBoundsException("size:" + pojoList.size() + ",index:" + end);
        }
        Class<?> propertyType = getPropertyType(pojoList.get(0), fieldName);// 能到这里证明list.size大于0
        // 验证是否为Number
        Assert.isTrue(Number.class.isAssignableFrom(propertyType), "必须是数字");

        double sum = 0d;
        int count = 0;
        for (int i = start; i < end; i++) {
            T obj = pojoList.get(i);
            double value = getDoubleValue(obj, fieldName);
            sum = MathUtils.add(sum, value);
            count++;
        }
        return count == 0 ? 0 : MathUtils.divide(sum, count, 2);
    }

    /**
     * 对集合中元素的指定属性求和。
     * <p>
     * 如，对User的age属性求和：
     * List<User> users = userService.findAll();
     * double avg = BeanUtils.sum(users, "age");
     *
     * @param pojoList
     * @param fieldName
     * @return
     */
    public static <T> double sum(List<T> pojoList, String fieldName) {
        Assert.notNull(pojoList);
        return sum(pojoList, fieldName, 0, pojoList.size());
    }

    /**
     * 对集合中元素的指定属性求和。（指定起始位置和向后计算的长度）
     *
     * @param pojoList
     * @param fieldName (只能是Number类型)
     * @param start     must >= 0
     * @param len       must >= 0
     * @return
     */
    public static <T> double sum(List<T> pojoList, String fieldName, int start, int len) {
        // 必须为正数
        Assert.isTrue(start >= 0, "start must >= 0");
        Assert.isTrue(len >= 0, "len must >= 0");
        // 验证是否非空
        Assert.notNull(pojoList);
        int end = start + len;
        // 验证是否可能越界
        if (isOutOfBounds(pojoList, end)) {
            throw new IndexOutOfBoundsException("size:" + pojoList.size() + ",index:" + end);
        }
        Class<?> propertyType = getPropertyType(pojoList.get(0), fieldName);// 能到这里证明list.size大于0
        // 验证是否为Number
        Assert.isTrue(Number.class.isAssignableFrom(propertyType), "必须是数字");

        double sum = 0d;
        for (int i = start; i < end; i++) {
            T obj = pojoList.get(i);
            double value = getDoubleValue(obj, fieldName);
            sum = MathUtils.add(sum, value);
        }
        return sum;
    }

    /**
     * Long 数组 转为 long 数组
     *
     * @param arrLong
     * @return
     */
    public static long[] covertArrLongToArrlong(Long[] arrLong) {
        long[] arrlong = new long[arrLong.length];
        for (int i = 0; i < arrLong.length; i++) {
            arrlong[i] = arrLong[i];
        }
        return arrlong;
    }

    /**
     * 获取PropertyDescriptor
     *
     * @param clazz
     * @return
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) {
        return org.springframework.beans.BeanUtils
                .getPropertyDescriptors(clazz);
    }

    /**
     * 获取PropertyDescriptor
     *
     * @param clazz
     * @param propertyName
     * @return
     */
    public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName) {
        // spring和apache的BeanUtils都有PropertyDescriptor的缓存，但是前者比后者快很多
        return org.springframework.beans.BeanUtils.getPropertyDescriptor(clazz, propertyName);
    }

    /**
     * 深拷贝,必须序列化
     *
     * @param src
     * @param <T>
     * @return
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static <T> List<T> deepCopy(List<T> src) throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(byteOut);
        out.writeObject(src);

        ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
        ObjectInputStream in = new ObjectInputStream(byteIn);
        @SuppressWarnings("unchecked")
        List<T> dest = (List<T>) in.readObject();
        return dest;
    }

    // 暂用于求平均值，如果出现空值视为0
    private static double getDoubleValue(Object obj, String field) {
        Number n = getProperty(obj, field);
        return n == null ? 0d : n.doubleValue();
    }

    /**
     * 封装jackson
     *
     * @param <T>
     * @author leo
     */
    public static abstract class TypeReference<T> extends com.fasterxml.jackson.core.type.TypeReference<T> {
    }

}